#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ide.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/atomic.h>

#define keydev_CNT         1
#define keydev_NAME        "key"
#define KEY0VALUE          0XF0
#define INVAKEY            0X00

struct key_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node  *nd;
    int key0;
    atomic_t keyvalue;
};

struct key_dev keydev;


static int key_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &keydev;

    return 0;
}

static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char value;
    struct key_dev *dev = filp->private_data;

    if(gpio_get_value(dev->key0)==0){
        while(!gpio_get_value(dev->key0));
        atomic_set(&dev->keyvalue,KEY0VALUE);
    }else{
        atomic_set(&dev->keyvalue,INVAKEY);
    }

    value = atomic_read(&dev->keyvalue);
    ret = copy_to_user(buf,&value,sizeof(value));
    return ret;
}

static ssize_t key_release(struct inode *inode, struct file *filp)
{
    struct key_dev *dev = filp->private_data;
    atomic_set(&dev->keyvalue,0);
        
    return 0;
}


static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open  = key_open,
    .read = key_read,
    .release = key_release,
};

static int key_probe(struct platform_device *dev)
{
    atomic_set(&keydev.keyvalue,INVAKEY);

    printk("key driver and device was matched!\r\n");

    if(keydev.major){
        keydev.devid = MKDEV(keydev.major,0);
        register_chrdev_region(keydev.devid,keydev_CNT,keydev_NAME);
    }
    else
    {
        alloc_chrdev_region(&keydev.devid,0,keydev_CNT,keydev_NAME);
        keydev.major = MAJOR(keydev.devid);
        keydev.minor = MINOR(keydev.devid);
    }

    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev,&key_fops);
    cdev_add(&keydev.cdev,keydev.devid,keydev_CNT);

    keydev.class = class_create(THIS_MODULE,keydev_NAME);
    if(IS_ERR(keydev.class)){
        return -PTR_ERR(keydev.class);
    }

    keydev.device = device_create(keydev.class,NULL,keydev.devid,NULL,keydev_NAME);
    if(IS_ERR(keydev.device)){
        return PTR_ERR(keydev.device);
    }

    keydev.nd = of_find_node_by_path("/key");
    if(keydev.nd == NULL){
        printk("gpio node nost find!\r\n");
        return -EINVAL;
    }
    keydev.key0 = of_get_named_gpio(keydev.nd,"key-gpio",0);
    if(keydev.key0 < 0){
        printk("can't get key-gpio\r\n");
        return -EINVAL;
    }

    gpio_request(keydev.key0,"key0");
    gpio_direction_input(keydev.key0);
    return 0;
}

static int key_remove(struct platform_device *dev)
{   
    gpio_free(keydev.key0);

    cdev_del(&keydev.cdev);
    unregister_chrdev_region(keydev.devid,keydev_CNT);
    device_destroy(keydev.class,keydev.devid);
    class_destroy(keydev.class);
    return 0;
}

static const struct of_device_id keys_of_match[] = {
    {.compatible = "atkalpha-gpiokey"},
    {}
};

MODULE_DEVICE_TABLE(of,keys_of_match);

static struct platform_driver key_platfrom_driver = {
    .driver = {
        .name = "imx6ul-key",
        .of_match_table = keys_of_match,
    },
    .probe = key_probe,
    .remove = key_remove,
};

static int __init keydriver_init(void)
{
    return platform_driver_register(&key_platfrom_driver);
}

static void __exit keydriver_exit(void)
{
    platform_driver_unregister(&key_platfrom_driver);
}


module_init(keydriver_init);
module_exit(keydriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GARY");